"""
@Project ：PythonInterfaceFrame
@File    ：conftest.py
@IDE     ：PyCharm
@Author  ：张成龙
@Date    ：2024/10/12 10:09
@explain ：
"""
import os.path
import logging
import os
import re
from logging import handlers
import subprocess
import time
import shutil
from urllib.parse import urlparse

import psutil

from utils.LoadEnvUtil import LoadEnvUtil
from utils.SendMessageUtil import SendMessageUtil

# 项目根路径
# BASE_DIR = os.path.dirname(__file__)
BASE_DIR = os.path.abspath(os.path.dirname(__file__))

"""
env 文件 配置项 Key

"""
# 机器人 webhook
WEBHOOK = "WEBHOOK"
# 通知描述
DESCRIPTION = "DESCRIPTION"
# 是否开启机器人通知
OPEN_OFFICER_NOTIFICATION = "OPEN_OFFICER_NOTIFICATION"
# 通知人员 list 集合
NOTIFIER_OFFICER = "NOTIFIER_OFFICER"


# 配置日志文件
# 初始化日志配置
def init_log_config():
    # 创建日志器
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    # 创建控制台处理器
    sh = logging.StreamHandler()

    # 创建文件处理器
    # 文件路径
    # Create file processor
    # File path
    log_dir = os.path.join(BASE_DIR, "log")
    os.makedirs(log_dir, exist_ok=True)  # 如果不存在就创建，存在就跳过
    log_path = os.path.join(log_dir, "log.log")

    fh = logging.handlers.TimedRotatingFileHandler(log_path, when="midnight", interval=1,
                                                   backupCount=7, encoding="UTF-8")
    # 创建格式化器
    f = "%(asctime)s %(levelname)s [%(name)s] [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
    formatter = logging.Formatter(f)

    # 把格式化处理器添加到处理器当中
    sh.setFormatter(formatter)
    fh.setFormatter(formatter)

    # 把处理器添加到日志器当中
    logger.addHandler(sh)
    logger.addHandler(fh)


"""
pytest_terminal_summary 是 Pytest 的一个内置钩子函数，它允许你在测试会话结束时，定制化地输出总结信息到终端
这一方法在 Pytest 测试运行完成后被调用，适用于添加额外统计信息、运行结果分析或其他总结性输出
在终端执行 pytest 命令之后 测试会话结束时触发以下条件
执行上传测试报告到文档中心
企业微信通知相关人员并统计测试数据(用例总数、失败用例数、成功用例数、跳过用例数、运行会话耗时、点击跳转文档中心地址)

参数 exitstatus :
    0 所有测试通过
    1 至少有一个测试用例失败
    2 用户手动中断测试执行(如Ctrl+C)
    3 测试用例未被正确收集(比如插件冲突、内部异常等)
    4 pytest 的命令行参数错误(传入了无效参数，比如拼错了选项)
    5 pytest 未找到测试用例(
      目录中没有测试文件；
      测试函数/类名不符合 pytest 命名规则；
      使用了过滤参数（如 -k 或 -m）后没有匹配上
      )

"""
# 机器人 webhook
webhook = LoadEnvUtil.get_env_params(WEBHOOK)
# 通知描述
desc = LoadEnvUtil.get_env_params(DESCRIPTION)
# 是否开启机器人通知
open_notifier = LoadEnvUtil.get_env_params(OPEN_OFFICER_NOTIFICATION, "false").lower()
# 通知人员 list 集合
notifier = LoadEnvUtil.parse_notifier_officer(NOTIFIER_OFFICER)
# 文档中心测试报告跳转地址
address = LoadEnvUtil.get_env_params("DOCUMENT_CENTER")


def run_allure_server(results_dir):
    """后台启动 allure serve 服务"""
    allure_path = shutil.which("allure")
    if not allure_path:
        raise RuntimeError("找不到 allure 命令，请确认环境变量已配置，或手动设置 allure 路径。")
    # cmd = [allure_path, "serve", "-h", "0.0.0.0", "-p", str(port), results_dir]
    # subprocess.Popen(cmd)
    cmd = [allure_path, "serve", results_dir]
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    pattern = re.compile(r"http://[^\s]+:\d+/")
    address = None

    # 监听输出
    while True:
        line = process.stdout.readline()
        if not line:
            break
        print(f"[Allure] {line.strip()}")  # 打印日志
        match = pattern.search(line)
        if match:
            address = match.group(0)
            break

    return address


def kill_allure_serve():
    """查找并杀掉已有 allure serve 进程"""
    for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
        try:
            if 'allure' in proc.info['name'] or \
                    (proc.info['cmdline'] and 'serve' in proc.info['cmdline']):
                print(f"[INFO] 发现旧 Allure 进程，已终止 PID: {proc.pid}")
                proc.kill()
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue


# 获取所有失败的用例 打印出来
def get_failed_tests_list(failed_tests, stats):
    if failed_tests > 0:
        failed_case_list = []
        print("\n==== 失败用例列表 ====")
        for report in stats.get("failed", []):
            print(f"- {report.nodeid}")
            # nodeid 表示 该测试用例的唯一标识符，由以下几部分组成：
            # <文件路径>::<类名>::<函数名>[<参数化信息（如果有）]
            # 例如： tests/test_login.py::TestLogin::test_fail
            failed_case_list.append(report.nodeid)
        return failed_case_list
    else:
        return "无"


def identify_platform(webhook_url: str) -> str:
    # 获取不同平台的不同前缀 从而判断是什么平台 钉钉 企业微信 飞书
    parsed = urlparse(webhook_url.split('?')[0])
    domain = parsed.netloc
    if "qyapi.weixin.qq.com" in domain:
        return "企业微信"
    elif "oapi.dingtalk.com" in domain:
        return "钉钉"
    elif "open.feishu.cn" in domain:
        return "飞书"
    else:
        return "未知平台"


def pytest_terminal_summary(terminalreporter, exitstatus):
    """在 Pytest 会话结束时计算总耗时并统计结果"""
    end_time = time.time()
    # 用pytest session start 记录中的时间作为会话开始时间
    started_time = terminalreporter._sessionstarttime
    total_time = round(end_time - started_time, 2)  # 保留两位小数的总耗时
    # 控制台的输出时间包括pytest插件加载 插件运行 测试用例运行 钩子函数运行 等所有时间综合
    # 在此统计的仅仅是 pytest 会话执行前后时间 不包括插件加载 失败重跑 钩子函数运行完毕的时间
    print(f"\n仅测试会话运行耗时: {total_time:.2f} 秒")
    print("\n==== pytest 会话结束 ====")

    # 获取测试结果统计
    stats = terminalreporter.stats

    skipped_tests = len(stats.get("skipped", []))  # 跳过测试数量
    failed_tests = len(stats.get("failed", []))  # 失败测试数量
    error_tests = len(stats.get('error', []))  # 错误测试数量
    passed_tests = len(stats.get("passed", []))  # 成功测试数量

    total_tests = terminalreporter._numcollected  # 总测试数量

    failed_test_cases_list = get_failed_tests_list(failed_tests, stats)

    print("\n==== 测试统计 ====")
    print(f"总测试用例数量: {total_tests}")
    print(f"成功: {passed_tests}")
    print(f"失败: {failed_tests}")
    print(f"失败: {error_tests}")
    print(f"跳过: {skipped_tests}")

    # 根据不同的退出状态输出不同的信息
    if exitstatus == 0:
        print("所有测试通过！")
    elif exitstatus == 1:
        print("有测试失败，请检查日志！")
    elif exitstatus == 2:
        print("测试用例未被正确收集，请检查路径或语法错误！")
    elif exitstatus == 3:
        print("测试被手动中断！")
    else:
        print("未知错误，请检查配置或环境问题。")

    # 启动 Allure 服务
    # try:
    #     # print("\n正在检查是否有旧的 Allure 进程...")
    #     # kill_allure_serve()
    #
    #     results_dir = "./report/allure-data"  # 你的报告路径
    #     print("\n启动 Allure 报告服务中...")
    #     # 获取局域网访问地址
    #     address = run_allure_server(results_dir)
    #     print(f"Allure 报告地址: {address}")
    # except Exception as e:
    #     print(f"启动 Allure 报告失败: {e}")
    #     address = "Allure 服务启动失败"

    """
    机器人通知
    """
    try:
        if open_notifier == "true":
            if identify_platform(webhook) == "企业微信":
                SendMessageUtil.send_work_wechat_results_msg(webhook, desc, total_tests, passed_tests, failed_tests,
                                                             error_tests, skipped_tests,
                                                             total_time, failed_test_cases_list, address)
                SendMessageUtil.send_work_wechat_notifying_officer(webhook, notifier)
            elif identify_platform(webhook) == "钉钉":
                SendMessageUtil.send_ding_talk_results_msg(webhook, desc, total_tests, passed_tests, failed_tests,
                                                           error_tests, skipped_tests,
                                                           total_time, failed_test_cases_list, address)
                SendMessageUtil.send_ding_talk_notifying_officer(webhook, notifier)
            elif identify_platform(webhook) == "飞书":
                SendMessageUtil.send_feishu_results_msg(webhook, desc, total_tests, passed_tests, failed_tests,
                                                        error_tests, skipped_tests,
                                                        total_time, failed_test_cases_list, address)
                SendMessageUtil.send_feishu_notifying_officer(webhook, notifier)
            else:
                print("未知的平台 -> 仅支持：企业微信、钉钉、飞书, 请配置正确的webhook!")
    except Exception as e:
        print(f"测试结果通知失败, 错误信息: {e}")
